package com.pig4cloud.pigx.contract.api.v1.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.common.customize.api.enums.AbnormalEnum;
import com.pig4cloud.pigx.common.customize.api.util.OpenApiUtil;
import com.pig4cloud.pigx.common.customize.enums.CommonDictEnum;
import com.pig4cloud.pigx.common.customize.i18n.util.MessageUtils;
import com.pig4cloud.pigx.common.customize.mysql.utils.MysqlUtil;
import com.pig4cloud.pigx.common.customize.utils.CommonUtil;
import com.pig4cloud.pigx.common.seata.utils.SeataUtils;
import com.pig4cloud.pigx.common.security.service.PigxUser;
import com.pig4cloud.pigx.contract.api.v1.service.PositionServiceV1;
import com.pig4cloud.pigx.contract.api.v1.utils.OpenApiContractUtilsV1;
import com.pig4cloud.pigx.contract.constant.I18nMessages;
import com.pig4cloud.pigx.contract.dto.AppContractMarginChangeDTO;
import com.pig4cloud.pigx.contract.dto.AppContractMultipleChangeDTO;
import com.pig4cloud.pigx.contract.entity.ContractPosition;
import com.pig4cloud.pigx.contract.entity.ContractPositionMarginDetail;
import com.pig4cloud.pigx.contract.enums.ContractOrderEnum;
import com.pig4cloud.pigx.contract.enums.ContractPositionEnum;
import com.pig4cloud.pigx.contract.enums.ContractPositionMarginDetailEnum;
import com.pig4cloud.pigx.contract.executer.order.supporter.OrderCalculator;
import com.pig4cloud.pigx.contract.executer.position.PositionExecutor;
import com.pig4cloud.pigx.contract.executer.position.supporter.PositionCalculator;
import com.pig4cloud.pigx.contract.mapper.ContractPositionMapper;
import com.pig4cloud.pigx.contract.mongo.entity.ContractSymbolMongo;
import com.pig4cloud.pigx.contract.open.api.dto.OpenApiMultipleChangeDTOV1;
import com.pig4cloud.pigx.contract.open.api.vo.OpenApiPositionVOV1;
import com.pig4cloud.pigx.contract.utils.ContractUtils;
import com.pig4cloud.pigx.contract.vo.AppContractCurrentPositionVO;
import com.pig4cloud.pigx.wallet.dto.WalletContractAccountChangeDTO;
import com.pig4cloud.pigx.wallet.enums.WalletContractAccountDetailEnum;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
 */
@Service
@AllArgsConstructor
public class PositionServiceImplV1 extends ServiceImpl<ContractPositionMapper, ContractPosition> implements PositionServiceV1 {

    private final OpenApiContractUtilsV1 apiContractUtils;

    private final ContractUtils contractUtils;

    private final MysqlUtil mysqlUtil;

    private final PositionExecutor positionExecutor;

    @Override
    public R<BigDecimal> getCurrentCount(String symbolName) {
        // 币对校验 自动抛对应异常
        ContractSymbolMongo mongo = apiContractUtils.getSymbolMongo(symbolName);
        try {
            ContractPosition position = baseMapper.selectOne(new QueryWrapper<ContractPosition>() {{
                eq("symbol_name", symbolName);
                select("sum(now_num) 'nowNum'");
            }});
            if (position == null) {
                return R.ok(BigDecimal.ZERO);
            }
            // 设置数量精度
            Integer tokenCurAccuracy = mongo.getTokenCurAccuracy();
            return R.ok(position.getNowNum().setScale(tokenCurAccuracy, RoundingMode.HALF_UP));
        } catch (Exception e) {
            throw OpenApiUtil.getError(AbnormalEnum.MSG.DISCONNECTED);
        }
    }

    @Override
    public R<BigDecimal> getProportion(String symbolName) {
        // 币对校验 自动抛对应异常
        apiContractUtils.getSymbolMongo(symbolName);
        try {
            // 根据持仓方向分组查询
            List<ContractPosition> selectList = baseMapper.selectList(new QueryWrapper<ContractPosition>() {{
                select("count( 1 ) 'count',side");
                eq("symbol_name", symbolName);
                groupBy("side");
            }});
            if (selectList.size() < 2) {
                return R.ok(BigDecimal.ZERO);
            }
            int many = 0;
            int empty = 0;
            for (ContractPosition e : selectList) {
                if (ContractOrderEnum.Side.MORE.getValue().equals(e.getSide())) {
                    many = e.getCount();
                }
                if (ContractOrderEnum.Side.EMPTY.getValue().equals(e.getSide())) {
                    empty = e.getCount();
                }
            }
            // 计算多空比
            BigDecimal decimal = BigDecimal.valueOf((float) many / empty).setScale(2, RoundingMode.HALF_UP);
            return R.ok(decimal);
        } catch (Exception e) {
            throw OpenApiUtil.getError(AbnormalEnum.MSG.DISCONNECTED);
        }
    }

    @Override
    public R multipleChange(OpenApiMultipleChangeDTOV1 dto) {
        // 币对非空校验
        if (dto.getSymbolName() == null || "".equals(dto.getSymbolName())) {
            throw OpenApiUtil.getError(AbnormalEnum.MSG.PARAM_EMPTY, "symbolName");
        }
        if (dto.getMultiple() == null) {
            throw OpenApiUtil.getError(AbnormalEnum.MSG.PARAM_EMPTY, "multiple");
        }
        // 币对校验 自动抛对应异常
        ContractSymbolMongo mongo = apiContractUtils.getSymbolMongo(dto.getSymbolName());
        // 杠杆大小范围校验
        if (dto.getMultiple() < 1 || dto.getMultiple() > mongo.getMultiple()) {
            throw OpenApiUtil.getError(AbnormalEnum.MSG.UNKNOWN_ORDER_COMPOSITION, "multiple < 1  ||  multiple > maxMultiple");
        }
        // 获取当前登录人
        PigxUser user = apiContractUtils.getUser();
        AppContractMultipleChangeDTO changeDTO = new AppContractMultipleChangeDTO();
        try {
            // 类型转换
            BeanUtils.copyProperties(dto, changeDTO);
            changeDTO.setSymbolId(mongo.getId());
            changeDTO.setUserId(user.getId());
        } catch (Exception e) {
            throw OpenApiUtil.getError(AbnormalEnum.MSG.DISCONNECTED);
        }
        positionExecutor.multipleChange(changeDTO);
        return R.ok();
    }

    /**
     * 逐仓调整保证金
     *
     * @param dto dto
     * @return r
     */
    @Override
    public R marginChange(AppContractMarginChangeDTO dto) {
        // 参数校验
        if (dto.getPositionId() == null) {
            throw OpenApiUtil.getError(AbnormalEnum.MSG.MANDATORY_PARAM_EMPTY_OR_MALFORMED, "positionId");
        }
        if (dto.getAmount() == null) {
            throw OpenApiUtil.getError(AbnormalEnum.MSG.MANDATORY_PARAM_EMPTY_OR_MALFORMED, "amount");
        }
        if (dto.getCalculationType() == null) {
            throw OpenApiUtil.getError(AbnormalEnum.MSG.MANDATORY_PARAM_EMPTY_OR_MALFORMED, "calculationType");
        }
        if (BigDecimal.ZERO.compareTo(dto.getAmount()) >= 0) {
            throw OpenApiUtil.getError(AbnormalEnum.MSG.UNKNOWN_ORDER_COMPOSITION, "amount < 0");
        }

        try {
            PigxUser user = apiContractUtils.getUser();

            // 查询仓位信息，并锁住
            ContractPosition condition = new ContractPosition();
            condition.setId(dto.getPositionId());
            // 确保是本人仓位
            condition.setUserId(user.getId());

            // 开启本地事务
            mysqlUtil.startLocalTx();
            ContractPosition position = contractUtils.getOneCurrentPosition(condition, true);
            ContractUtils.checkCurrentPosition(position, false);

            if (Objects.equals(CommonDictEnum.YesOrNo.YES.getValue(), position.getNeedLiquidate())) {
                // 仓位强平中
                throw OpenApiUtil.getError(AbnormalEnum.MSG.UNSUPPORTED_OPERATION, MessageUtils.get(I18nMessages.POSITION_IS_STRONG_IN_FLAT));
            }

            // 判断是否是逐仓
            if (!Objects.equals(ContractPositionEnum.Mode.ISOLATED.getValue(), position.getMode())) {
                // 异常操作
                throw OpenApiUtil.getError(AbnormalEnum.MSG.UNSUPPORTED_OPERATION, MessageUtils.get(I18nMessages.ABNORMAL_OPERATION));
            }

            // 查询币对信息
            ContractSymbolMongo symbol = contractUtils.getSymbolById(position.getSymbolId());
            ContractUtils.checkSymbol(symbol);

            // 设置精度
            dto.setAmount(CommonUtil.setUSDTScale(dto.getAmount()));
            // JVM根据计算类型修改仓位保证金
            if (Objects.equals(CommonDictEnum.CalculationType.ADD.getValue(), dto.getCalculationType())) {
                // 增加
                position.setMargin(position.getMargin().add(dto.getAmount()));
            } else {
                // 减少
                position.setMargin(position.getMargin().subtract(dto.getAmount()));

                // 计算标准保证金
                BigDecimal positionValue = OrderCalculator.getOrderValue(position.getOpenAvgPrice(), position.getNowNum());
                BigDecimal standardMargin = PositionCalculator.getStandardMargin(positionValue, position.getMultiple());
                if (position.getMargin().compareTo(standardMargin) < 0) {
                    // 超过可操作仓位保证金上限
                    throw OpenApiUtil.getError(AbnormalEnum.MSG.UNSUPPORTED_OPERATION, MessageUtils.get(I18nMessages.EXCEED_THE_LIMIT_OF_OPERABLE_POSITION_MARGIN));
                }
            }

            // JVM重新计算强平价和是否需要强平
            PositionCalculator.calculateLiquidateIsolated(position, symbol);
            // 判断是否需要强平
            if (Objects.equals(CommonDictEnum.YesOrNo.YES.getValue(), position.getNeedLiquidate())) {
                // 将导致仓位强平，请修改调整金额
                throw OpenApiUtil.getError(AbnormalEnum.MSG.UNSUPPORTED_OPERATION, MessageUtils.get(I18nMessages.POSITION_WILL_BE_STRONGLY_CLOSED));
            }

            // 根据加/减获得操作类型
            WalletContractAccountDetailEnum.OperationType operationType = Objects.equals(CommonDictEnum.CalculationType.ADD.getValue(), dto.getCalculationType()) ?
                    WalletContractAccountDetailEnum.OperationType.ADD
                    : WalletContractAccountDetailEnum.OperationType.MINUS;

            // 开启全局事务
            SeataUtils.startGlobalTx();

            // 修改钱包余额
            WalletContractAccountChangeDTO param = new WalletContractAccountChangeDTO(user.getId(),
                    dto.getAmount(),
                    null,
                    position.getSymbolId(),
                    position.getSymbolName(),
                    WalletContractAccountDetailEnum.BusinessType.POSITION_MARGIN,
                    operationType,
                    false);
            R<?> r = contractUtils.changeAsset(Collections.singletonList(param));
            if (!r.getIsSuccess()) {
                throw new RuntimeException(r.getMsg());
            }

            // 修改仓位信息
            if (baseMapper.updateById(position) != 1) {
                throw OpenApiUtil.getError(AbnormalEnum.MSG.UNSUPPORTED_OPERATION, MessageUtils.get(I18nMessages.FAILED_TO_MODIFY_POSITION_INFORMATION));
            }

            // 提交全局事务
            SeataUtils.commitGlobalTx();
            // 提交本地事务
            mysqlUtil.commitLocalTx();

            // 发送重算全仓强平价消息
            contractUtils.sendPositionCrossCalculate(position.getUserId());

            // 发送保证金明细消息
            ContractPositionMarginDetail detail = new ContractPositionMarginDetail();
            detail.setPositionId(position.getId());
            detail.setMargin(dto.getAmount());
            detail.setBusinessType(ContractPositionMarginDetailEnum.BusinessType.CHANGE_MARGIN.getValue());
            // 根据保证金操作类型获取保证金明细操作类型
            ContractPositionMarginDetailEnum.OperationType marginOperationType = Objects.equals(CommonDictEnum.CalculationType.ADD.getValue(), dto.getCalculationType()) ?
                    ContractPositionMarginDetailEnum.OperationType.ADD
                    : ContractPositionMarginDetailEnum.OperationType.MINUS;
            detail.setOperationType(marginOperationType.getValue());
            detail.setLiquidatePrice(position.getLiquidatePrice());
            contractUtils.sendSavePositionMarginDetailMq(Collections.singletonList(detail));
            return R.ok();
        } catch (Throwable e) {
            // 回滚全局事务
            SeataUtils.rollbackGlobalTx();
            // 回滚本地事务
            mysqlUtil.rollbackLocalTx();
            throw e;
        }
    }

    /**
     * 获取当前持仓的仓位信息
     *
     * @return list
     */
    public R<List<OpenApiPositionVOV1>> getCurrentPosition(String symbolName) {
        // 获取登录用户
        PigxUser user = apiContractUtils.getUser();
        // 如果币对不为空则校验币对
        apiContractUtils.getSymbolMongo(symbolName);
        try {
            List<ContractPosition> selectList = baseMapper.selectList(new QueryWrapper<ContractPosition>() {{
                eq("user_id", user.getId());
                eq("symbol_name", symbolName);
            }});
            List<OpenApiPositionVOV1> list = new ArrayList<>();
            for (ContractPosition position : selectList) {
                // 类型转换
                AppContractCurrentPositionVO vo = new AppContractCurrentPositionVO();
                BeanUtils.copyProperties(position, vo);
                // 计算未实现盈亏、收益率，设置标记价
                contractUtils.computeFloatProfit(vo);
                // 计算页面保证金和保证金率
                contractUtils.computeMarginRate(vo);
                OpenApiPositionVOV1 positionVo = new OpenApiPositionVOV1();
                BeanUtils.copyProperties(vo, positionVo);
                positionVo.setId(position.getId());
                // 计算可用数量（USDT）
                positionVo.setUNowNum(vo.getNowNum().multiply(vo.getMarkPrice()));
                positionVo.setUAvailableNum(vo.getAvailableNum().multiply(vo.getMarkPrice()));
                positionVo.setUClosingNum(vo.getClosingNum().multiply(vo.getMarkPrice()));
                // 字典项翻译
                positionVo.setMode(ContractPositionEnum.Mode.findName(positionVo.getMode()));
                positionVo.setSide(ContractOrderEnum.Side.findName(positionVo.getSide()));
                // 精度
                contractUtils.setScaleBySymbol(positionVo, positionVo.getSymbolName());
                list.add(positionVo);
            }
            return R.ok(list);
        } catch (Exception e) {
            throw OpenApiUtil.getError(AbnormalEnum.MSG.DISCONNECTED);
        }
    }

}
